package cn.edu.sjtu.permissionleak;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import cn.edu.sjtu.DedexerPlus;
import cn.edu.sjtu.permission.PermissionChecker;

public class InvokeCompleted extends PermissionChecker {
	private HashSet<String> receiver;
	
	private HashSet<String> mi;
	
	private HashMap<String, Set<String>> endsOfClass;
	
	public static HashSet<String> methodSet;

	static {
		methodSet = new HashSet<String>();
		initMethodSet();
	}
	
	public InvokeCompleted(String path) {
		super(path);

		receiver = new HashSet<String>();
		mi = new HashSet<String>();
		endsOfClass = new HashMap<String, Set<String>>();
		
	}

	public static boolean isCheckMethod(String m) {
		if (!methodSet.contains(m)) {
			String tm = m.substring(m.lastIndexOf("/") + 1);
			if (!methodSet.contains(tm)) {
				return false;
			}
		}
		return true;
	}
	
	private static void initMethodSet() {
		methodSet.add("java/lang/Thread/start");
		methodSet.add("java/lang/reflect/Method/invoke");
		
		methodSet.add("android/content/ContentResolver/query");
		methodSet.add("android/content/ContentResolver/insert");
		methodSet.add("android/content/ContentResolver/update");
		methodSet.add("android/content/ContentResolver/delete");
		
		methodSet.add("registerReceiver");
		methodSet.add("sendBroadcast");
		methodSet.add("sendOrderedBroadcast");
		methodSet.add("sendStickyBroadcast");
		methodSet.add("sendStickyOrderedBroadcast");
		methodSet.add("startActivity");
		methodSet.add("startActivityForResult");
		methodSet.add("startService");
		methodSet.add("bindService");
	}
	
	@Override
	public void checkPermission(DedexerPlus ddxp) {
		if (receiver != null) {
			for (String s : receiver) {
				if (DedexerPlus.invokeMap.containsKey(s)) {
					HashSet<String> initSet; 
					if (DedexerPlus.getPermissionForMethod(s) != null) {
						initSet = (HashSet<String>) DedexerPlus.getPermissionForMethod(s).clone();
					} else {
						initSet = new HashSet<String>();
					}
					HashSet<String> called = new HashSet<String>();
					LinkedList<String> methods = new LinkedList<String>();
					methods.add(s);
					mi.add(s);
					called.add(s);
					while (methods.size() != 0) {
						String m = methods.removeFirst();
						LinkedList<String> list = (LinkedList<String>) DedexerPlus.invokeMap.get(m);
						if (list != null) {
							for (String method: list) {
								if (DedexerPlus.invokeMap.containsKey(method)) {
									String c = method.substring(0, method.lastIndexOf("."));
									String f = method.substring(method.lastIndexOf(".") + 1);
									HashSet<String> perset = DedexerPlus.getPermissionForMethod(method);
									if (perset != null)
										initSet.addAll(perset);
									
									if (!called.contains(method)) {
										methods.addLast(method);
										mi.add(method);
										called.add(method);
									}
									// add all sub class method call
									List<String> cl = DedexerPlus.reExtendMap.get(c);
									if (cl != null) {
										for (String sc : cl) {
											String nm = sc + "." + f;
											if (!called.contains(nm)) {
												methods.addLast(nm);
												mi.add(nm);
												called.add(nm);
											}
										}
									}
								}
							}
						}
					}
					//System.out.println("Permission " + s + ":");
					//for (String per : initSet) {
					//	System.out.println("\t" + per);
					//}
				}
			}
		}
		for (String m : mi) {
			System.out.println(m);
		}
	}

	@Override
	protected void setPermissionMap(String path, HashMap<String, String> map) {
		// TODO Auto-generated method stub
		
	}

	
	@Override
	public void analyse(HashSet<String> pset, String func, String[] regValue) {	// get the ends from code
		String startMethod = null, cl = null;
		if (regValue != null) {
			if (regValue.length > 1) {
				if (regValue[1].contains("IntentFilter(")) {
					if (regValue[0] != null && regValue[0].contains("instance: ")) {
						cl = regValue[0].substring(regValue[0].indexOf("instance: ") + "instance: ".length()).replace("/", ".");
						startMethod = cl + ".onReceive"; 
						receiver.add(startMethod);						
						if (regValue[1] != null && regValue[1].contains("IntentFilter")) {
							String t = regValue[1].substring("IntentFilter(".length(), regValue[1].length() - 1);
							String[] actions = t.split(",");
							Set<String> set;
							if (endsOfClass.containsKey(cl)) {
								set = endsOfClass.get(cl);
							} else {
								set = new HashSet<String>();
							}
							for (String action : actions) {
								set.add(action);
							}
							endsOfClass.put(cl, set);
						}
					}
				}
			}
			if (regValue[0] != null && regValue[0].contains("Intent(")) {
				int i1 = regValue[0].indexOf("CLASS:");
				int i2 = regValue[0].indexOf(";", i1);
				if (i1 != -1) {
					cl = regValue[0].substring(i1 + 6, i2);
					if (func.contains("startService")) {
						for (String s : smethods) {
							receiver.add(cl + "." + s);
						}
					} else {
						for (String s : amethods) {
							receiver.add(cl + "." + s);
						}
					}
				}
				i1 = regValue[0].indexOf("ACTION:");
				i2 = regValue[0].indexOf(";", i1);
				if (i1 != -1 && i2 != -1) {
					String action = regValue[0].substring(i1 + "ACTION:".length(), i2);
					Set<String> set;
					if (endsOfClass.containsKey(cl)) {
						set = endsOfClass.get(cl);
					} else {
						set = new HashSet<String>();
					}
					set.add(action);
					endsOfClass.put(cl, set);
					//System.out.println(cl + " " + action);
				}
			}
		}
	}
	
	public void initEndsFromXML(String path) {
		DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder dombuilder = domfac.newDocumentBuilder();
			InputStream is = new FileInputStream(path);
			Document doc = dombuilder.parse(is);
			Node app = doc.getElementsByTagName("application").item(0);
			NodeList ends = app.getChildNodes();
			for (int i = 0; i < ends.getLength(); i++) {
				Node end = ends.item(i);
				if (end.getNodeType() == Node.ELEMENT_NODE) {
					if (end.getNodeName().equals("activity") || 
						end.getNodeName().equals("service")  ||
						end.getNodeName().equals("receiver") ||
						end.getNodeName().equals("provider")) {
						
						String enabled = null;
						String exported = null;
						if (end.getAttributes().getNamedItem("android:enabled") != null) {
							enabled = end.getAttributes().getNamedItem("android:enabled").getNodeValue();
						}
						if (end.getAttributes().getNamedItem("android:exported") != null) {
							exported = end.getAttributes().getNamedItem("android:exported").getNodeValue();
						}
						if ((enabled == null && exported == null) || (enabled != null && enabled.equals("true") && exported != null && exported.equals("true"))) {
							String c = end.getAttributes().getNamedItem("android:name").getNodeValue();
							if (end.getNodeName().equals("provider")) {
								receiver.add(c + ".onCreate");
							} else if (end.getNodeName().equals("receiver")) {
								receiver.add(c + ".onReceive");
							} else if (end.getNodeName().equals("service")) {
								for (String s : smethods) {
									receiver.add(c + "." + s);
								}
							} else {
								for (String s : amethods) {
									receiver.add(c + "." + s);
								}
							}
							
							NodeList ifs = end.getChildNodes();
							for (int j = 0; j < ifs.getLength(); j++) {
								Node intentfilter = ifs.item(j);
								if (intentfilter.getNodeType() == Node.ELEMENT_NODE && 
									intentfilter.getNodeName().equals("intent-filter")) {
									Set<String> set;
									if (endsOfClass.containsKey(c)) {
										set = endsOfClass.get(c);
									} else {
										set = new HashSet<String>();
									}
									
									NodeList acts = intentfilter.getChildNodes();
									for (int k = 0; k < acts.getLength(); k++) {
										Node act = acts.item(k);
										if (act.getNodeName().equals("action")) {
											String a = act.getAttributes().getNamedItem("android:name").toString();
											set.add(a.substring("android:name=\"".length(), a.length() - 1));
											//System.out.println(c + " " + act.getAttributes().getNamedItem("android:name"));
										}
									}
									endsOfClass.put(c, set);
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private String[] findPermissionLeakMethod(String dm) {
		if (DedexerPlus.reInvokeMap != null) {
			LinkedList<String> res = new LinkedList<String>();
			LinkedList<String> stack = new LinkedList<String>();
			stack.add(dm);
			while (stack.size() != 0) {
				String m = stack.removeFirst();
				if (DedexerPlus.reInvokeMap.containsKey(m)) {
					stack.addAll(DedexerPlus.reInvokeMap.get(m));
				} else {
					if (DedexerPlus.invokeMap.containsKey(m))
						res.add(m);
				}
			}
			return res.toArray(new String[res.size()]);
		}
		return new String[0];
	}
	
	public void findPermissionLeakInterface(String dm) {

		String[] methods = findPermissionLeakMethod(dm);
		
		for (String method : methods) {
			int index = method.lastIndexOf(".");
			String func = method.substring(index + 1);
			String cl = method.substring(0, index);
			String cll = cl.substring(cl.lastIndexOf(".") + 1);
			
			if (endsOfClass.containsKey(cl) || endsOfClass.containsKey(cll)) {
				Set<String> s;
				if (endsOfClass.containsKey(cl)) {
					s = endsOfClass.get(cl);
				} else {
					s = endsOfClass.get(cll);
				}
				for (String br : s) {
					if (func.equals("onReceive")) {
						// result
						System.out.println("Receiver: " + br + " " + cl + " " + dm);
					} else {
						ArrayList<String> smList = new ArrayList<String>(Arrays.asList(smethods));
						ArrayList<String> amList = new ArrayList<String>(Arrays.asList(amethods));
						if (smList.contains(func) && amList.contains(func)) {
							String father = DedexerPlus.getFatherClass(cl);
							if (cl.contains("Service") || (father != null && father.contains("Service"))) {
								System.out.println("Service: " + br + " " + cl + " " + dm);
							}
							if (cl.contains("Activity") || (father != null && father.contains("Activity"))) {
								System.out.println("Activity: " + br + " " + cl + " " + dm);
							}
						} else {
							if (smList.contains(func)) {
								System.out.println("Service: " + br + " " + cl + " " + dm);
							} else if (amList.contains(func)) {
								System.out.println("Activity: " + br + " " + cl + " " + dm);
							}
						}
					}
				}
			}
		}
	}
	
	public void findPermissionLeakAction() {
		System.out.println("### Permission Leak: ###");
		for (String m : dangerMethods) {
			findPermissionLeakInterface(m);
		}
	}
	
	public void findDeadMethod(HashMap<String, List<String>> map, HashMap<String, List<String>> remap) {
		LinkedList<String> deadMethodList = new LinkedList<String>();
		if (map != null && remap != null) {
			Iterator i = map.entrySet().iterator();
			while (i.hasNext()) {
				Map.Entry e = (Entry)i.next();
				if (!remap.containsKey(e.getKey().toString())) {
					if (e.getKey().equals("java.lang.reflect.Method.invoke")) {
						System.out.println();
					}
					deadMethodList.add(e.getKey().toString());
				}
			}
		}
		Iterator<String> it = deadMethodList.iterator();
		while (it.hasNext()) {
			String method = it.next();
			if (checkAutoInvoke(method)) {
				it.remove();
			}
		}
		for (String method : deadMethodList) {
			//System.out.println("Dead Method total: " + deadMethodList.size());
			//System.out.println("@@@ " + method + " @@@");
		}
	}
	
	public boolean checkAutoInvoke(String method) {
		String c = method.substring(0, method.lastIndexOf("."));
		String func = method.substring(method.lastIndexOf(".") + 1);
		
		if (func.equals("<init>") || func.equals("<clinit>")) {
			return true;
		} else {
			String f = DedexerPlus.getFatherClass(c);
			if (f != null) {
				ArrayList<String> amList = new ArrayList<String>(Arrays.asList(amethods));
				if (amList.contains(func)) {
					if (f.equals("android.app.Activity")) {
						return true;
					}
				}
				ArrayList<String> smList = new ArrayList<String>(Arrays.asList(smethods));
				if (smList.contains(func)) {
					if (f.equals("android.app.Service")) {
						return true;
					}
				}
				
				if (func.equals("onReceive") && f.equals("android.content.BroadcastReceiver")) {
					return true;
				}
			}
		}
		return false;
	}
	
	public static void main(String[] args) {
		InvokeCompleted ic = new InvokeCompleted(null);
		ic.initEndsFromXML("D:\\Temp\\AndroidExp\\AndroidManifest.xml");
		ic.findPermissionLeakInterface("D:\\Temp\\AndroidExp\\AndroidManifest.xml");
	}
	
	private final String[] dangerMethods = {"android.location.LocationManager.addGpsStatusListener", "android.location.LocationManager.addNmeaListener", "android.location.LocationManager.addProximityAlert", "android.location.LocationManager.addTestProvider", "android.location.ILocationManager$Stub$Proxy.addGpsStatusListener",
			"android.location.ILocationManager$Stub$Proxy.addProximityAlert", "android.location.LocationManager.addGpsStatusListener", "android.location.LocationManager.addNmeaListener", "android.location.LocationManager.addProximityAlert", "com.android.internal.telephony.ITelephony$Stub$Proxy.endCall", "com.android.internal.telephony.ITelephony$Stub$Proxy.call", 
			"android.telephony.PhoneNumberUtils.getNumberFromIntent", "android.telephony.TelephonyManager.getCompleteVoiceMailNumber", "android.hardware.Camera.native_setup", "android.hardware.Camera.open", "android.media.MediaRecorder.setVideoSource", "android.app.ContextImpl$ApplicationPackageManager.deletePackage", "android.content.pm.IPackageManager$Stub$Proxy.deletePackage", "android.content.pm.PackageManager.deletePackage", "android.app.ContextImpl$ApplicationPackageManager.deletePackage", 
			"android.app.ContextImpl$ApplicationPackageManager.installPackage", "android.content.pm.IPackageManager$Stub$Proxy.installPackage", "android.content.pm.PackageManager.installPackage", "android.app.ContextImpl$ApplicationPackageManager.installPackage", "android.accounts.AccountManagerService$SimWatcher.onReceive", "android.telephony.PhoneNumberUtils.isVoiceMailNumber", "android.telephony.TelephonyManager.getDeviceId", "android.telephony.TelephonyManager.getDeviceSoftwareVersion", "android.telephony.TelephonyManager.getLine1AlphaTag", 
			"android.telephony.TelephonyManager.getLine1Number", "android.telephony.TelephonyManager.getSimSerialNumber", "android.telephony.TelephonyManager.getSubscriberId", "android.telephony.TelephonyManager.getVoiceMailAlphaTag", "android.telephony.TelephonyManager.getVoiceMailNumber", "android.telephony.TelephonyManager.listen", "com.android.internal.telephony.CallerInfo.markAsVoiceMail", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getDeviceId", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getDeviceSvn", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getIccSerialNumber", 
			"com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getLine1AlphaTag", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getLine1Number", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getSubscriberId", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getVoiceMailAlphaTag", "com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getVoiceMailNumber", "com.android.internal.telephony.ITelephony$Stub$Proxy.isSimPinEnabled", "android.os.IPowerManager$Stub$Proxy.crash", "android.os.IPowerManager$Stub$Proxy.reboot", "android.os.PowerManager.reboot", "android.os.RecoverySystem.bootCommand", "android.os.RecoverySystem.installPackage",
			"android.os.RecoverySystem.rebootWipeUserData", "android.media.AudioRecord.init", "android.media.MediaRecorder.setAudioSource", "android.speech.SpeechRecognizer.cancel", "android.speech.SpeechRecognizer.handleCancelMessage", "android.speech.SpeechRecognizer.handleStartListening", "android.speech.SpeechRecognizer.handleStopMessage", "android.speech.SpeechRecognizer.startListening", "android.speech.SpeechRecognizer.stopListening", "android.telephony.gsm.SmsManager.copyMessageToSim", "android.telephony.gsm.SmsManager.deleteMessageFromSim", "android.telephony.gsm.SmsManager.sendDataMessage", "android.telephony.gsm.SmsManager.sendMultipartTextMessage", "android.telephony.gsm.SmsManager.sendTextMessage", 
			"android.telephony.gsm.SmsManager.updateMessageOnSim", "android.telephony.SmsManager.copyMessageToIcc", "android.telephony.SmsManager.deleteMessageFromIcc", "android.telephony.SmsManager.sendDataMessage", "android.telephony.SmsManager.sendMultipartTextMessage", "android.telephony.SmsManager.sendTextMessage", "android.telephony.SmsManager.updateMessageOnIcc", "com.android.internal.telephony.ISms$Stub$Proxy.sendData", "com.android.internal.telephony.ISms$Stub$Proxy.sendMultipartText", "com.android.internal.telephony.ISms$Stub$Proxy.sendText", "android.app.IActivityManager$Stub$Proxy.shutdown", "android.os.INetworkManagementService$Stub$Proxy.shutdown", "android.os.storage.IMountService$Stub$Proxy.shutdown"};
	
	private final String[] smethods = {"onBind", "onConfigurationChanged", "onCreate", "onDestroy", "onLowMemory", "onRebind", "onStart", "onStartCommand", "onTaskRemoved",
			"onTrimMemory", "onUnbind"};
	private final String[] amethods = {"onActionModeFinished", "onActionModeStarted", "onAttachFragment", "onAttachedToWindow", "onBackPressed", "onConfigurationChanged", "onContentChanged", "onContextItemSelected",
			"onContextMenuClosed", "onCreateContextMenu", "onCreateDescription", "onCreateOptionsMenu", "onCreatePanelMenu", "onCreatePanelView", "onCreateThumbnail", "onCreateView", "onCreateView",
			"onDetachedFromWindow", "onGenericMotionEvent", "onKeyDown", "onKeyLongPress", "onKeyMultiple", "onKeyShortcut", "onKeyUp", "onLowMemory", "onMenuItemSelected", "onMenuOpened", "onOptionsItemSelected", 
			"onOptionsMenuClosed", "onPanelClosed", "onPrepareOptionsMenu", "onPreparePanel", "onRetainNonConfigurationInstance", "onSearchRequested", "onTouchEvent", "onTrackballEvent", "onTrimMemory", "onUserInteraction", "onWindowAttributesChanged",
			"onWindowFocusChanged", "onWindowStartingActionMode", "onActivityResult", "onApplyThemeResource", "onChildTitleChanged", "onCreate", "onCreateDialog", "onCreateDialog", "onDestroy", "onNewIntent", "onPause", "onPostCreate", "onPostResume", "onPrepareDialog", 
			"onPrepareDialog", "onRestart", "onRestoreInstanceState", "onResume", "onSaveInstanceState", "onStart", "onStop", "onTitleChanged", "onUserLeaveHint"};
}